﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

/**
 * An EnvironmentStack.
 * Basic stack implementation that holds our current
 * Environment, as well as Environments we may wish
 * to jump back to.
 */
public class EnvStack
{
    // Member vars here.
    private Stack<EnvBase> m_stack;
    private State m_state;
    private EnvBase m_target;

    /**
     * Basic FSM states.
     */
    enum State
    {
        kState_INVALID = -1,
        kState_Idle,
        kState_Push,
        kState_PopOnce,
        kState_PopAll
    }

    /**
     * Our Constructor.
     * It totally constructs.
     */
    public EnvStack()
    {
        m_stack = new Stack<EnvBase>();
        m_target = null;
        m_state = State.kState_Idle;
    }

    /**
     * Return our current Environment
     */
    public EnvBase currentEnv() { return m_stack.Peek(); }

    /**
     * Returns a Texture2D of the current Env's current render call.
     * Can conveniently be called in the constructor of an Env to get
     * the _previous_ Env's render as a Texture2D.
     */
    public Texture2D currentEnvAsTexture()
    {
        GraphicsDeviceManager graphics = Game.getGame().getGraphics();
        // Create the new RenderTarget
        RenderTarget2D renderTarget = new RenderTarget2D(graphics.GraphicsDevice, Game.kScreenWidth, Game.kScreenHeight, 1, SurfaceFormat.Color);

        // Get the original target (so we can set it back later)
        RenderTarget screen = graphics.GraphicsDevice.GetRenderTarget(0);

        // Use the new RenderTarget for drawing
        graphics.GraphicsDevice.SetRenderTarget(0, renderTarget);
        graphics.GraphicsDevice.Clear(ClearOptions.Target, new Color(255, 255, 255, 0), 0, 0);
        
        // Begin drawing as usual
        Game.getGame().getSpriteBatch().Begin(SpriteBlendMode.AlphaBlend, SpriteSortMode.BackToFront, SaveStateMode.None);
        m_stack.Peek().render();
        Game.getGame().getSpriteBatch().End();

        // Set the graphics device back to status quo
        graphics.GraphicsDevice.SetRenderTarget(0, screen as RenderTarget2D);

        // Get our new tile texture
        return renderTarget.GetTexture();
    }

    /**
     * Our update function.
     * Updates our stack, doing any necessary pushing
     * or popping. Returns false if the stack is
     * empty.
     * @param dt
     */
    public bool update(double dt)
    {
        switch (m_state)
        {
            case State.kState_Push:
                m_stack.Push(m_target);
                break;
            case State.kState_PopOnce:
                m_stack.Pop();
                if (m_target != null)
                {
                    m_stack.Push(m_target);
                }
                break;
            case State.kState_PopAll:
                m_stack.Clear();
                if (m_target != null)
                {
                    m_stack.Push(m_target);
                }
                break;
        }
        if (m_state != State.kState_Idle)
        {
            Game.getGame().getConsole().print("Transitioning to: " + ((m_stack.Count() <= 0) ? "None" : m_stack.Peek().ToString()));
            m_state = State.kState_Idle;
            m_target = null;
        }

        if (m_stack.Count() <= 0)
        {
            return false;
        }

        EnvBase current = m_stack.Peek();
        current.update(dt);
        return true;
    }

    /**
     * Our render function.
     * Calls our current Environment's render.
     */
    public void render()
    {
        m_stack.Peek().render();
    }

    /**
     * Pushes an Environment onto the stack.
     * Note that the Environment is not actually pushed
     * until the next update.
     * @param target
     */
    public void push(EnvBase target)
    {
        m_state = State.kState_Push;
        m_target = target;
    }

    /**
     * A shortcut for calling popOnce with a null target.
     */
    public void popOnce() { popOnce(null); }
    /**
     * Pops the top Environment off the stack.
     * Can be given a new target Environment to push onto
     * the stack after the pop. Note that the pop doesn't
     * actually happen until the next update.
     * @param target
     */
    public void popOnce(EnvBase target)
    {
        m_state = State.kState_PopOnce;
        m_target = target;
    }

    /**
     * A shortcut to popAll with a null target.
     */
    public void popAll() { popAll(null); }
    /**
     * Pops all Environments off our stack.
     * Can be given a new target Environment to
     * push onto the stack after all pops. Note
     * that the pops don't happen until the next
     * update.
     * @param target
     */
    public void popAll(EnvBase target)
    {
        m_state = State.kState_PopAll;
        m_target = target;
    }
}
